package com.goa.core;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.goa.cache.SessionMap;
import com.goa.config.SemaphoreManager;
import com.goa.entity.DeviceBase;
import com.goa.entity.DeviceCron;
import com.goa.entity.DeviceResult;
import com.goa.entity.MethodInvocationData;
import com.goa.mapper.DeviceBaseMapper;
import com.goa.mapper.DeviceResultMapper;
import com.goa.mapper.DeviceTaskMapper;
import com.goa.mapper.DynamicTableMapper;
import com.goa.parse.ModbusDataProcessor;
import com.goa.parse.ByteBufUtils;
import com.goa.parse.CheckJson;
import com.goa.parse.VerificationData;
import com.goa.utils.ARTUtils;
import com.goa.utils.DynamicMethodInvoker;
import com.goa.utils.JdbcUtil;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.util.AttributeKey;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Semaphore;

/**
 * @author chuyongshuo
 * @version 1.0
 * @date 2024/10/12 9:44
 */
@Slf4j
@Component
public class ReceiveMessage {

    @Autowired
    DeviceBaseMapper deviceBaseMapper; //设备表

    @Autowired
    DeviceTaskMapper deviceTaskMapper; //定时任务表

    @Autowired
    DeviceResultMapper deviceResultMapper; //映射表

    @Autowired
    DynamicTableMapper dynamicTableMapper; //动态SQL表

    /**
     * 接收消息处理逻辑
     * @param ctx
     * @param msg
     * @throws Exception
     */
    protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
        Channel channel =null;
        String requestId ="";
            channel = ctx.channel();
            requestId = (String) channel.attr(AttributeKey.valueOf("requestId")).get();
            String hexDump = ByteBufUtil.hexDump(msg);

            if (requestId==null||"".equals(requestId)){
                //处理心跳包/注册包
                //1.查询设备表 是否有与之关联的包信息
                //2.获取该地址的ip 更新到数据库;
                DeviceBase deviceBase = deviceBaseMapper.queryDeviceByBeat(hexDump);
                log.info("注册包数据{},连接的设备是{}",hexDump,deviceBase);
                if (deviceBase!=null){
                    //1.处理设备表的设备状态/设备连接ip及port/设备首次连接时间
                    deviceBase.setBeginTime(new Date());//设备连接时间
                    deviceBase.setMacStatus(1);//设备连接正常
                    deviceBase.setStartLoginIp(channel.remoteAddress().toString());//获取连接地址
                    Boolean flag =deviceBaseMapper.updateDeviceStatusAndIpById(deviceBase);
                   if (flag){
                       //加入到设备信任列表
                       SessionMap.deviceMap.put(channel,deviceBase);
                   }
                }else {
                    log.info("没有响应头,也没有查询出设备信息");
                }
            }else {
                //使用try-catch-finally 保证响应的消息及时释放锁，而注册包心跳包是设备主动发送,不涉及锁释放
                try {
                    DeviceBase deviceBase= SessionMap.deviceMap.get(channel);
                    if (deviceBase!=null){
                        //校验16进制和JSON数据
                        if (CheckJson.isJson2Hex(hexDump)) {
                            handleJsonMessage(ctx, hexDump, channel,requestId);
                        } else {
                            //正常处理响应逻辑
                            handleHexMessage(hexDump, msg, channel,requestId);
                        }
                    }else {
                        log.info("还未信任设备,不处理该条数据");
                    }
                }catch (Exception e){
                    e.printStackTrace();
                }finally {
                    //保证锁一定被释放。
                    releaseSemaphore(channel, requestId);
                    ctx.writeAndFlush("服务端收到：" + new Date() + " " + msg + "\r\n");
                }
            }
    }


    /**
     * 16进制接收消息方法
     * @param hexDump
     * @param msg
     * @param channel
     */
    private void handleHexMessage(String hexDump, ByteBuf msg, Channel channel,String requestId) throws Exception {
        byte[] bytes = ByteBufUtils.toByteArray(msg);
        boolean isHex = VerificationData.checkHex16(bytes);
        if (isHex) {
            log.info("16进制==={}===指令校验通过,响应头是{},", hexDump,requestId);
            //校验通过后解析数据 存储数据库
            /*//1.解析出该条指令的地址位,根据地址位关联出数据库的相关解析信息
            String address = ParseModbusUtils.parseAddress(bytes); //地址位
            //2.根据地址位是查询数据库*/
            boolean checkeSuccess = VerificationData.checkHex16(bytes);
            if(checkeSuccess){
                //CRC校验成功
                //3.因为携带的定时任务主键，去映射表查询出当前定时任务主键，那么解析的信息查询出来就可以解析了
                    //解析数据 动态创建表结构 插入数据
                    //根据主键在任务表 查询出该定时任务相关信息
                    //然后去结构表查询出映射关系
                    String[] split = requestId.split("\\-");
                    requestId=split[1];
                    log.info("响应头携带的设备ID是{},定时任务主键是{}",split[0],split[1]);
                    DeviceCron deviceCron= deviceTaskMapper.selectDeviceCronById(requestId);
                    List<DeviceResult>  deviceResultList =deviceResultMapper.selectDeviceResultByQueryId(requestId);
                DeviceBase device = deviceBaseMapper.queryDeviceById(split[0]);
                if (deviceCron!=null){
                     //在此可以进行一些列非空判断 然后进行JDBC连接动态创建表结构
                     String tableName = deviceCron.getTableName(); //动态表名称
                     String createSQL = JdbcUtil.appendSql(tableName, deviceResultList);
                     //DDL语句使用JDBC连接创建 不使用mybatis 此处可以使用缓存 记录该表是否创建过、减少JDBC连接次数。
                     JdbcUtil.executeDDL(createSQL);
                     //解析出的数据map = {A_current=13360, A_voltage=25392, B_voltage=12337, C_voltage=13872, C_current=12337, B_current=25392}
                     Map<String, Integer> map = ModbusDataProcessor.processModbusData(bytes, deviceResultList);
                     List<String> columnList =new ArrayList<>(); //[A_current, B_current, C_current, A_voltage, B_voltage, C_voltage]
                     columnList.add("device_id");
                     List<String> valueList =new ArrayList<>();//存储数据列
                     valueList.add(split[0]);
                    if (deviceResultList!=null &&deviceResultList.size()>0)
                     //获取到所有字段
                     for (DeviceResult deviceResult : deviceResultList) {
                         String resultColumn = deviceResult.getResultColumn();
                         columnList.add(resultColumn);//传递给SQL进行字段拼接
                         //map默认没有顺序 此地方主要是保证字段顺序和数值顺序插入一致。
                         Integer value = map.get(resultColumn);
                         //使用反射动态执行各个字段计算方法
                         MethodInvocationData invocationData = new MethodInvocationData(deviceResult.getInvokerMethod(),value,device);
                         Object v= DynamicMethodInvoker.invokeMethod(new ARTUtils(),invocationData.getMethodName(),invocationData.getParams() );
                         valueList.add(v.toString());
                     }
                    columnList.add("timestamp");
                    valueList.add(String.valueOf(System.currentTimeMillis()));
                    boolean insertFlag = dynamicTableMapper.insertData(tableName,columnList,valueList);
                    log.info("插入是否成功{},表名称==={},数据列==={}",insertFlag,tableName,map);
                 }
            }else {
                //CRC校验失败 记录日志
                log.error("16进制==={}===CRC校验失败,", hexDump);
            }
        } else {
            log.error("16进制==={}===指令校验失败,", hexDump);
        }
    }

    /**
     * JSON接收消息方法
     * @param ctx
     * @param hexDump
     * @param channel
     * @throws Exception
     */
    private void handleJsonMessage(ChannelHandlerContext ctx, String hexDump, Channel channel,String requestId) throws Exception {
        String jsonString = CheckJson.hexToString(hexDump);
        ObjectMapper objectMapper = new ObjectMapper();

        try {
            Object json = objectMapper.readValue(jsonString, Object.class);
            log.info("有客户端发来的数据。地址:{}->JSON数据内容是{}", channel.remoteAddress(), objectMapper.writeValueAsString(json));
        } catch (JsonProcessingException e) {
            log.error("JSON 解析失败", e);
            ctx.writeAndFlush("无效的 JSON 格式\r\n");
        }
    }

    /**
     * 根据发送指令会携带给客户端一个key 通过key释放对应的锁。而没有key的是没有锁的。
     * @param channel
     * @param requestId
     */
    private void releaseSemaphore(Channel channel,  String requestId) {
        if (requestId != null) {
//            String ipAddress = extractIpAddress(channel.remoteAddress().toString());
//            Semaphore semaphore = SemaphoreManager.semaphoreMap.get(ipAddress);
            Semaphore semaphore = SemaphoreManager.semaphoreMap.get(channel);
            if (semaphore != null) {
                semaphore.release();
                log.info("释放了任务类型 " + channel.remoteAddress() + " 的锁");
            }
        }
    }

    /**
     * 解析出连接设备的IP
     * @param ip socketip地址形式
     * @return ip
     */
    private String extractIpAddress(String ip) {
        int startIndex = ip.indexOf('/') + 1;
        int colonIndex = ip.indexOf(':');
        return ip.substring(startIndex, colonIndex);
    }

}
